<template>
  <span>{{ state.displayValue }}</span>
</template>
<script setup lang="ts" name="VabCount">
import {
  requestAnimationFrame,
  cancelAnimationFrame
} from "./requestAnimationFrame";

const props = defineProps({
  startVal: {
    type: Number,
    required: false,
    default: 0
  },
  endVal: {
    type: Number,
    required: false,
    default: 0
  },
  duration: {
    type: Number,
    required: false,
    default: 3000
  },
  autoplay: {
    type: Boolean,
    required: false,
    default: true
  },
  decimals: {
    type: Number,
    required: false,
    default: 0
  },
  decimal: {
    type: String,
    required: false,
    default: "."
  },
  separator: {
    type: String,
    required: false,
    default: ","
  },
  prefix: {
    type: String,
    required: false,
    default: ""
  },
  suffix: {
    type: String,
    required: false,
    default: ""
  },
  useEasing: {
    type: Boolean,
    required: false,
    default: true
  },
  easingFn: {
    type: Function,
    default(t, b, c, d) {
      return (c * (-Math.pow(2, (-10 * t) / d) + 1) * 1024) / 1023 + b;
    }
  }
});

const emits = defineEmits(["mountedCallback", "callback"]);

const isNumber = val => {
  return !isNaN(parseFloat(val));
};

const formatNumber = num => {
  num = num.toFixed(props.decimals);
  num += "";
  const x = num.split(".");
  let x1 = x[0];
  const x2 = x.length > 1 ? props.decimal + x[1] : "";
  const rgx = /(\d+)(\d{3})/;
  if (props.separator && !isNumber(props.separator)) {
    while (rgx.test(x1)) {
      x1 = x1.replace(rgx, `$1${props.separator}$2`);
    }
  }
  return props.prefix + x1 + x2 + props.suffix;
};

const state = reactive({
  localStartVal: props.startVal,
  displayValue: formatNumber(props.startVal),
  printVal: null,
  paused: false,
  localDuration: props.duration,
  startTime: null,
  timestamp: null,
  remaining: null,
  rAF: null
});

const countDown = computed(() => {
  return props.startVal > props.endVal;
});

watch(
  () => props.startVal,
  () => {
    if (props.autoplay) {
      start();
    }
  }
);
watch(
  () => props.endVal,
  () => {
    if (props.autoplay) {
      start();
    }
  }
);

onMounted(() => {
  if (props.autoplay) {
    start();
  }
  emits("mountedCallback");
});

onUnmounted(() => {
  cancelAnimationFrame(state.rAF);
});

const start = () => {
  state.localStartVal = props.startVal;
  state.startTime = null;
  state.localDuration = props.duration;
  state.paused = false;
  state.rAF = requestAnimationFrame(count);
};
const pauseResume = () => {
  if (state.paused) {
    resume();
    state.paused = false;
  } else {
    pause();
    state.paused = true;
  }
};
const pause = () => {
  cancelAnimationFrame(state.rAF);
};
const resume = () => {
  state.startTime = null;
  state.localDuration = +state.remaining;
  state.localStartVal = +state.printVal;
  requestAnimationFrame(count);
};

const reset = () => {
  state.startTime = null;
  cancelAnimationFrame(state.rAF);
  state.displayValue = formatNumber(props.startVal);
};

const count = computed(timestamp => {
  if (!state.startTime) state.startTime = timestamp;
  state.timestamp = timestamp;
  const progress = state.timestamp - state.startTime;
  state.remaining = state.localDuration - progress;

  if (props.useEasing) {
    if (countDown) {
      state.printVal =
        state.localStartVal -
        props.easingFn(
          progress,
          0,
          state.localStartVal - props.endVal,
          state.localDuration
        );
    } else {
      state.printVal = props.easingFn(
        progress,
        state.localStartVal,
        props.endVal - state.localStartVal,
        state.localDuration
      );
    }
  } else {
    if (countDown) {
      state.printVal =
        state.localStartVal -
        (state.localStartVal - props.endVal) * (progress / state.localDuration);
    } else {
      state.printVal =
        state.localStartVal +
        (props.endVal - state.localStartVal) * (progress / state.localDuration);
    }
  }
  if (countDown) {
    state.printVal =
      state.printVal < props.endVal ? props.endVal : state.printVal;
  } else {
    state.printVal =
      state.printVal > props.endVal ? props.endVal : state.printVal;
  }

  state.displayValue = formatNumber(state.printVal);
  if (progress < state.localDuration) {
    state.rAF = requestAnimationFrame(count);
  } else {
    emits("callback");
  }
});
</script>
